#include "GameEngine/GameEngine.hpp"

/**
 * Initialize a new game with a map.
 * Start also the clock
 */
GameEngine::GameEngine(bool p_isServeurSide) :
m_listOfPlayers(),		m_listOfShot(),		m_map(0),			m_counterIdPlayer(0)
{
	this->m_isServerSide 	= p_isServeurSide;
	this->GenerateMap();

	this->m_Clock = new sf::Clock();
	this->m_Clock->Reset();

	this->m_isRunning = true;

	//this->Launch();
}

GameEngine::~GameEngine()
{
	//Debug::printTrace(Debug::information, "GameEngine/GameEngine.Destructor", "Start to free memory");
	this->Stop();

	this->RemoveAllPlayers();
	this->RemoveAllShots();

	delete this->m_Clock;
	delete this->m_map;
	//Debug::printTrace(Debug::information, "GameEngine/GameEngine.Destructor", "End to free memory");
}

/**
 * Stop the gameEngine
 */
void GameEngine::Stop()
{
	this->m_isRunning = false;
	this->Wait();
}
/**
 * loop which move the objects inside the game
 */
void GameEngine::Run()
{
	//Debug::printTrace(Debug::information, "GameEngine/GameEngine.Run", "Start loop");

	while(m_isRunning)
	{
		float l_ElapsedTime = this->m_Clock->GetElapsedTime();
		this->m_Clock->Reset();
		//Debug::printTrace(Debug::information, "GameEngine/GameEngine.Run", "Inside loop");

		this->RemoveOldShoot();

		this->SpawnPlayer();

		for (unsigned int i(0) ; i < this->m_listOfPlayers.size() ; i++)
		{
			m_listOfPlayers[i]->Move(l_ElapsedTime);
		}

		for (unsigned int i(0) ; i < this->m_listOfShot.size() ; i++)
		{
			for (unsigned int j(0) ; j < this->m_listOfPlayers.size() ; j++)
			{
				if(m_listOfShot[i]->CollisionWithPlayer(m_listOfPlayers[j]))
				{
					if(this->m_isServerSide)
					{
						m_listOfPlayers[j]->TakeDamage(m_listOfShot[i]->GetDammage());
						this->NotifyObserver(GameEngine::NOTIFICATION_CODE_PLAYER_HIT, m_listOfPlayers[j]);
						if(m_listOfPlayers[j]->GetState() == Character::DEAD)
						{
							m_listOfPlayers[j]->AddDeath();
							m_listOfShot[i]->GetFrom()->AddKill();
							this->NotifyObserver(GameEngine::NOTIFICATION_CODE_PLAYER_DIE,  m_listOfPlayers[j]);
							this->NotifyObserver(GameEngine::NOTIFICATION_CODE_PLAYER_KILL, m_listOfShot[i]->GetFrom());
						}
					}
				}
			}

			m_listOfShot[i]->Move(l_ElapsedTime);
		}

		sf::Sleep(0.01f);
	}

}
/**
 * return the player with a specified Id
 */
Character* GameEngine::GetPlayerById(unsigned int p_id)
{
	for (unsigned int i(0) ; i < this->m_listOfPlayers.size() ; i++)
    {
        if(p_id == m_listOfPlayers[i]->GetID())
        {
            return m_listOfPlayers[i];
        }
    }

    return 0;
}
/**
 * Add a player, notify observers and return the new player instance
 * (Depreciated)
 */
Character* GameEngine::AddPlayer()
{
    Character* c = new Character(this->m_map);

    this->m_listOfPlayers.insert(this->m_listOfPlayers.end(), c);

    this->NotifyObserver(NOTIFICATION_CODE_NEW_PLAYER, c);

    return c;
}
/**
 * Create a new player and set all his parameter
 * return a character pointer
 */
Character* GameEngine::AddPlayer(
        		unsigned int p_id, 	string p_name, 		Character::States p_status,
        		float p_positionX, 	float p_positionY, 	int p_directionV,
        		int p_directionH, 	unsigned int p_nbKill,
        		unsigned int p_nbDeath)
{
	Character* c = new Character(this->m_map);

	c->SetName(p_name);
	c->SetState(p_status);
	c->Setposition(sf::Vector2f(p_positionX, p_positionY));
	c->SetDirectionHorizontal(p_directionH);
	c->SetDirectionVertical(p_directionV);
	c->SetKill(p_nbKill);
	c->SetDeath(p_nbDeath);

	if(p_id == 0)
		c->SetID(++m_counterIdPlayer);
	else
		c->SetID(p_id);

	this->m_listOfPlayers.insert(this->m_listOfPlayers.end(), c);

	this->NotifyObserver(NOTIFICATION_CODE_NEW_PLAYER, c);

	return c;
}
/**
 * spawn waiting player and dead player if the gameEngine is serverSide
 *
 */
void GameEngine::SpawnPlayer()
{
	if(m_isServerSide)
	{
		for (unsigned int i(0) ; i < this->m_listOfPlayers.size() ; i++)
		{
			if(m_listOfPlayers[i]->GetState() != Character::ALIVE)
			{
				if(m_listOfPlayers[i]->Spawn())
					this->NotifyObserver(NOTIFICATION_CODE_SPAWN_PLAYER, m_listOfPlayers[i]);
			}
		}
	}
}
/**
 * Remove a player and notify observers
 */
void GameEngine::RemovePlayer(Character* character)
{
    Character* c = character;
    character->Move(0);
    for (unsigned int i(0) ; i < this->m_listOfPlayers.size() ; i++)
    {
        if(character == m_listOfPlayers[i])
        {
        	delete character;
            m_listOfPlayers.erase(m_listOfPlayers.begin() + i);
            break;
        }
    }



    this->NotifyObserver(NOTIFICATION_CODE_DELETE_PLAYER, c);
}
/**
 * Delete all players and notify the observers
 */
void GameEngine::RemoveAllPlayers()
{
    for (unsigned int i(0) ; i < this->m_listOfPlayers.size() ; i++)
    {
        Character* c = m_listOfPlayers[i];

        delete m_listOfPlayers[i];

        this->NotifyObserver(NOTIFICATION_CODE_DELETE_PLAYER, c);
    }
    this->m_listOfPlayers.clear();
}
/**
 * Generate a semple Squarre map with borders
 */
void GameEngine::GenerateMap()
{
	delete m_map;
	m_map = new Map(DefaultValue::MAP_NB_TILES_X, DefaultValue::MAP_NB_TILES_Y);
	m_map->CreateBorderArroundTheMap();
}

/**
 * Delete all shots and notify the observers
 */
void GameEngine::RemoveAllShots()
{
    for (unsigned int i(0) ; i < this->m_listOfShot.size() ; i++)
    {
        Shot* c = m_listOfShot[i];

        delete m_listOfShot[i];

        this->NotifyObserver(NOTIFICATION_CODE_DELETE_SHOT, c);
    }
    this->m_listOfShot.clear();
}

/**
 * Add a shot and notify observers
 */
void GameEngine::Shoot(Character* p_player, sf::Vector2f p_target)
{
	if(p_player->CanShoot())
	{
		sf::Vector2f p_origin = p_player->Getposition();
		float angle;

		angle = FunctionTrigo::Angle(sf::Vector2f(p_origin.x - p_target.x, p_origin.y - p_target.y));

		this->Shoot(p_player, angle);
	}
}
/**
 * Add a shot and notify observers
 */
void GameEngine::Shoot(Character* p_player, float p_angle)
{
	Shot* c = new Shot(this->m_map, p_player, p_angle);

	p_player->Shoot();

	this->m_listOfShot.insert(this->m_listOfShot.end(), c);

	this->NotifyObserver(NOTIFICATION_CODE_NEW_SHOT, c);
}

/**
 * remove old shots (with status DEAD)
 */
void GameEngine::RemoveOldShoot()
{
	unsigned int i = 0;

    while (i < this->m_listOfShot.size())
    {
        if(m_listOfShot[i]->GetState() == Shot::DEAD)
        {
        	Shot* s = m_listOfShot[i];

            m_listOfShot.erase(m_listOfShot.begin() + i);

            this->NotifyObserver(NOTIFICATION_CODE_DELETE_SHOT, s);

            delete s;
        }
        else
        	i++;
    }
}
